import fnmatch
import os
import posixpath
import re
import sys
from ast import AST, Attribute, iter_fields, Name

if sys.version_info[0] == 3:
    string_types = (str,)
else:
    string_types = (basestring,)  # noqa

IGNORED_DIRS = [
    '*.egg-info',
    '.bzr',
    '.cache',
    '.git',
    '.hg',
    '.idea',
    '.svn',
    '.tox',
    '__pycache__',
    '_build',
    'bower_components',
    'CVS',
    'htmlcov',
    'node_modules',
    'var',
    'venv*',
    'static',
    'geckodriver',
]

IGNORED_PATH_REGEXPS = [
    '^.*shuup/admin/static/shuup_admin.*',  # autogenerated
    '^.*shuup/xtheme/static/*',  # autogenerated
    '^build/',  # built files
]

IGNORED_PATTERNS = [
    '*-bundle.js',
    '*.bat',
    '*.bz2',
    '*.csv',
    '*.dat',
    '*.doctree',
    '*.eot',
    '*.gif',
    '*.gz',
    '*.ico',
    '*.inv',
    '*.jpg',
    '*.map',  # source map
    '*.min.js',
    '*.mo',
    '*.otf',
    '*.pickle',
    '*.png',
    '*.pyc',
    '*.sqlite3',
    '*.svg',
    '*.ttf',
    '*.woff',
    '*.woff2',
    '*.zip',
    '*.xls',
    '*.xlsx',
    '.coverage',
    '_version.py',
    'coverage.xml',
    'Makefile',
    'vendor.js',
]


def find_files(
        roots, generated_resources=None,
        ignored_patterns=IGNORED_PATTERNS,
        ignored_dirs=IGNORED_DIRS,
        ignored_path_regexps=IGNORED_PATH_REGEXPS,
        allowed_extensions=None
):
    """
    Find files in `roots` with ignores, `generated_resources` handling etc.

    :param roots: Root directory or directories
    :type roots: str
    :param generated_resources:
        Output set of generated resources (mutated during find_files)
    :type generated_resources: set
    :param ignored_patterns: fnmatch file patterns to ignore
    :type ignored_patterns: Iterable[str]
    :param ignored_dirs: fnmatch directory patterns to ignore
    :type ignored_dirs: Iterable[str]
    :param ignored_path_regexps: Path regexps to ignore
    :type ignored_path_regexps: Iterable[str]
    :param allowed_extensions:
        Extensions (really filename suffixes) to ignore (optional)
    :type allowed_extensions: Iterable[str]|None
    :return: Iterable of file paths
    :rtype: Iterable[str]
    """
    if generated_resources is None:
        generated_resources = set()
    if isinstance(roots, string_types):
        roots = [roots]
    if isinstance(allowed_extensions, string_types):
        allowed_extensions = set([allowed_extensions])
    for root in roots:
        for (path, dirs, files) in os.walk(root):
            path = posixpath.normpath(path.replace(os.sep, "/"))
            _remove_ignored_directories(path, dirs, ignored_dirs, ignored_path_regexps)
            for filename in files:
                filepath = posixpath.join(path, filename)
                if not all(not fnmatch.fnmatch(filename, x) for x in ignored_patterns):
                    continue
                if not _check_allowed_extension(filepath, allowed_extensions):
                    continue
                yield filepath


def _check_allowed_extension(filepath, allowed_extensions):
    if allowed_extensions is None:
        return True
    return any(filepath.endswith(extension) for extension in allowed_extensions)


def _remove_ignored_directories(path, dirs, ignored_dirs, ignored_path_regexps):
    matches = set()
    for ignored_dir in ignored_dirs:
        matches.update(set(dir for dir in dirs if fnmatch.fnmatch(dir, ignored_dir)))

    for ignore_re in ignored_path_regexps:
        matches.update(dir for dir in dirs if re.match(ignore_re, posixpath.join(path, dir)))

    for ignored_dir in matches:
        dirs.remove(ignored_dir)


class XNodeVisitor(object):
    def visit(self, node, parents=None):
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
        return visitor(node=node, parents=parents)

    def generic_visit(self, node, parents=None):
        parents = (parents or []) + [node]
        for field, value in iter_fields(node):
            if isinstance(value, list):
                for item in value:
                    if isinstance(item, AST):
                        self.visit(item, parents)
            elif isinstance(value, AST):
                self.visit(value, parents)


def dotify_ast_name(name):
    if isinstance(name, Attribute):
        return "%s.%s" % (dotify_ast_name(name.value), name.attr)
    if isinstance(name, Name):
        return name.id
    return "<%s>" % name.__class__.__name__


def get_assign_first_target(assign):
    for target in assign.targets:
        id = getattr(target, "id", None)
        if id:
            return id
